Spring xml扩展机制

介绍

Spring框架从2.0版本开始,提供了基于Schema风格的XML扩展机制,允许开发者扩展最基本的spring配置文件,这样我们就可以编写自定义的xml bean解析器然后集成到Spring IoC容器中。很多支持Spring的框架,比如Dubbo、mybatis都提供了对Spring xml扩展的支持。

Xml扩展大概有以下几个步骤:

  • 编写xml schema来描述自定义元素
  • 编写自定义类
  • 编写NamespaceHandler的实现类
  • 编写BeanDefinitionParser实现类
  • 把上述组件注册到Spring

编写自定义schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.com/schema/product"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mycompany.com/schema/product"
elementFormDefault="qualified"
attributeFormDefault="unqualified">

<xsd:import namespace="http://www.springframework.org/schema/beans"/>

<xsd:element name="robot">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="brand" type="xsd:string" use="required"/>
<xsd:attribute name="type" type="xsd:string" use="required"/>
<xsd:attribute name="height" type="xsd:int"/>
<xsd:attribute name="weight" type="xsd:float"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>

编写自定义类

1
2
3
4
5
6
7
public class Robot {

private String brand;
private String type;
private int height;
private float weight;
}

然后我们就可以在xml中定义如下的robot元素:

1
<product:robot id="bb8" brand="StarWar" type="bb8" height="100" weight="20"/>

编写NamespaceHandler

NamespaceHandler用于解析我们自定义名字空间下的所有元素,目前我们要解析上面的product:robot元素。
NamespaceHandler里面只有3个方法:

  • init()会在NamespaceHandler初始化的时候被调用。
  • BeanDefinition parse(Element, ParserContext) - 当Spring遇到一个顶层元素的时候被调用。
  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext) - 当Spring遇到一个属性或嵌套元素的时候调用.

Spring提供了默认实现类NamespaceHandlerSupport,我们只需在init的时候注册每个元素的解析器即可。

1
2
3
4
5
6
7
8
public class ProductNamespaceHandler extends NamespaceHandlerSupport {

@Override
public void init() {
//遇到robot元素的时候交给RobotBeanDefinitionParser来解析
registerBeanDefinitionParser("robot", new RobotBeanDefinitionParser());
}
}

编写BeanDefinitionParser

这样在解析xml过程中遇到robot元素时,Spring会交给RobotBeanDefinitionParser来解析。RobotBeanDefinitionParser取出相应的属性然后设置到bean中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class RobotBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

@Override
protected Class<?> getBeanClass(Element element) {
//robot元素对应Robot对象类型
return Robot.class;
}

@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {

String brand = element.getAttribute("brand");
String type = element.getAttribute("type");
String height = element.getAttribute("height");
String weight = element.getAttribute("weight");

//把对应的属性设置到bean中
if(StringUtils.hasText(brand))
builder.addPropertyValue("brand", brand);

if(StringUtils.hasText(type))
builder.addPropertyValue("type", type);

if(StringUtils.hasText(height))
builder.addPropertyValue("height", height);

if(StringUtils.hasText(weight))
builder.addPropertyValue("weight", weight);
}
}

注册handler和schema

为了让Spring在解析xml的时候能够感知到我们的自定义元素,我们需要把namespaceHandler和xsd文件放到2个指定的配置文件中,这2个文件都位于META-INF目录中。

META-INF/spring.handlers

`spring.handlers`文件包含了xml schema uri和handler类的映射关系,比如:

http\://www.mycompany.com/schema/product=spring.xml.ext.schema.ProductNamespaceHandler

这表示遇到http://www.mycompany.com/schema/product命名空间的时候会交给ProductNamespaceHandler来处理。

注意上面的冒号转义。
key部分必须和xsd文件中的targetNamespace值保持一致。

META-INF/spring.schemas

http\://www.mycompany.com/schema/product.xsd=META-INF/product.xsd

最后测试下

写个Spring配置文件products.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:my="http://www.mycompany.com/schema/product"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.com/schema/product
http://www.mycompany.com/schema/product.xsd">

<product:robot id="bb8" brand="StarWar" type="bb8" height="100" weight="20"/>

</beans>

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:products.xml" })
public class SchemaTest {

@Autowired
@Qualifier("bb8")
private Robot robot;

@Test
public void propertyTest() {
assertNotNull(robot);

String brand = robot.getBrand();
String type = robot.getType();
int height = robot.getHeight();
float weight = robot.getWeight();

assertEquals("Brand should be bb8.", "bb8", brand);
assertEquals("Type should be bb8.", "bb8", type);
assertEquals("Height should be 100.", 100, height);
assertEquals("Weight should be 20.", 20, weight);
}
}

参考资料